/* Evaluator.m:
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 *  
 *
 */

#import <foundation/NSString.h>
#import <foundation/NSUtilities.h>

#import	"ScrollViewExtras.h"
#import "Evaluator.h"
#import "EOFDelegateAdaptorCategory.h"
 

#define FAILURE NXLocalizedString("Failure:", NULL, \
	"Message given to user when an operation has failed.")

#define CANNOT_CONNECT NXLocalizedString("Cannot connect to database", NULL, \
	"Message given to user to explain what fails. ")

#define OK NXLocalizedString("OK", NULL, "Okay to continue ")

@implementation Evaluator

- initWithModelFile:(NSString *)filePath
{
    char path[MAXPATHLEN];
	BOOL		result=NO;
    
    [super init];
  		
	// open the model file and connect to the database  
    model = [[[EOModel alloc] 	
				initWithContentsOfFile: filePath] autorelease];
				 
    if (!model)
		return nil;
  
    
    adaptor = [EOAdaptor adaptorWithModel:model];
    if (!adaptor) 
		return nil;
   
    context = [adaptor createAdaptorContext];
	if (!context)  	
		return nil;
    
        
    channel = [context createAdaptorChannel];
	
	if (!channel)   	
		return nil;
   
    if (![channel isOpen])
		 result = [channel openChannel];
	if (result)
		[resultsView sprintf:"Connection successful\n"];
	else
		[resultsView sprintf:"Connection failed\n"];
	
    [[NXBundle mainBundle] getPath:path forResource:"SQLWindow" ofType:"nib"];
	[NXApp loadNibFile:path owner:self withNames:NO];
	[(Window *)[resultsView window] setDelegate:self];
	[[resultsView window] setTitleAsFilename:[filePath cString]];
	[[queryView docView] selectAll:self];
	[[resultsView window] makeKeyAndOrderFront:self];
	
	/*----------------------------------------------------------------------
	 *	set up the channel delegate to SybaseDelegate for Sybase model files
	 * 	use EOFDelegateCategory for Oracle model files
	 *--------------------------------------------------------------------*/

	if ([[adaptor name] isEqualToString:@"Sybase"]) {
		sybaseDelegate = [[SybaseDelegate alloc] init];
	    [channel setDelegate:sybaseDelegate];
	}
	else {
		sybaseDelegate = nil;
	    [channel setDelegate:[[EOFDelegateAdaptorCategory alloc] init]];
	}
	 
	 return self;
}

- free
{
	[model release];
	[adaptor release];
	[context release];
	[channel release];
	if (sybaseDelegate)
		[sybaseDelegate release];
	
	return [super free];
}

- evaluate:sender
{
	BOOL	result = NO;
	int	charCount;
    char *buffer = NULL;
	NSDictionary *row;

	/*----------------------------------------------------------------------
	 *	get the SQL statement
	 *--------------------------------------------------------------------*/

	if ((charCount = [[queryView docView] textLength]) > 0) {
	buffer = malloc((charCount+1) * sizeof(char));
	[[queryView docView] getSubstring:buffer start:0 length:charCount];
	buffer[charCount] = 0;
	while (charCount > 0 && buffer[--charCount] == '\n')
	    buffer[charCount] = 0;
	[resultsView sprintf:"%s\n", buffer];
	
	}
	else {
	[resultsView sprintf:"Can't execute an empty string!\n"];
	return self;
	}
	
	/*----------------------------------------------------------------------
	 *	evaluate the SQL statement
	 *--------------------------------------------------------------------*/

	[context beginTransaction]; 
	result = [channel evaluateExpression:
			[[NSString alloc] initWithCString:(const char *)buffer]];
	
	if (result){
		[resultsView sprintf:"SQL successfully evaluated\n"];
		
		while ([channel isFetchInProgress] == YES) {
				// get the array of attributes from the result set 
			attributes = [channel describeResults];
		
				// since the EO Framework attributes are stored as Attribute1,
				// Attribute2, etc., build a dictionary to do conversion.
				
				 [self buildColumnNamesDictionary:attributes];
		
				// The NSDictionary contains all the data for one row
		while ((row  = [channel fetchAttributes: attributes withZone:
									[adaptor zone]]) != nil) 									
				// read data back 
			[self readDataFromDict:row ];
	 
		}
		[context commitTransaction];

	}
	else {
			// do clean-up if SQL statement cannot be evaluated. 
		[resultsView sprintf:"SQL evaluation failed\n"];
		if ([channel isFetchInProgress] == YES)
			[channel cancelFetch];
		[context rollbackTransaction];
	}	
	free(buffer);
    return self;
}



/* Build a dictionary where the Keys are Attribute1, Attribute2, etc.,
 * while the values are the actual column names as specified in the
 * database.
 */
- buildColumnNamesDictionary:(NSArray *) attributesArray
{
	NSEnumerator	*enumerator;
	EOAttribute  	*attribute;
	NSMutableArray	*keys = [NSMutableArray array];
	NSMutableArray	*values = [NSMutableArray array];
	
	enumerator = [attributesArray objectEnumerator];
	
	while ((attribute = [enumerator nextObject]) != nil) {
		[keys addObject:[attribute name]];
		[values addObject:[attribute columnName]];
	}
	columnNamesDict = [NSDictionary dictionaryWithObjects:values
					forKeys: keys];
	return self;
}

/* Cycle through the key and value pairs of the dictionary 
 * and print them to result view
 *  
 */
- readDataFromDict:(NSDictionary *) dictionary
{
	NSEnumerator		*keyEnumerator;
	NSString *			key;
	NSString *			newKey;
	id					value;
	char				buffer[1024];
	SybaseRowType		rowType=0;
	
	keyEnumerator = [dictionary keyEnumerator];
	if (sybaseDelegate)
		rowType = [sybaseDelegate currentRowType];
	
	//print the full dictionary for Sybase regular rows or if the Oracle adaptor is used
	if ( ((sybaseDelegate) &&  (rowType == SybaseRegularRow ))
				|| (sybaseDelegate == nil) )
	{
	while ((key = [keyEnumerator nextObject]) != nil) {
		newKey = [columnNamesDict objectForKey:[key description]];
		 
		value = [dictionary objectForKey:key];
		sprintf(buffer, "%s: \t %s\n", [[newKey description] cString],
					[[(NSObject *)value description] cString]);
		[resultsView sprintf:buffer];
		}
	}
	else {
		// This must be one special sybase row
		switch (rowType) {
		case SybaseComputeRow:
		case SybaseReturnStatusRow:
			// print one row of data only
			key = [keyEnumerator nextObject];
			value = [dictionary objectForKey:key];
			sprintf(buffer, "Row type: %s \t %s\n", (rowType == SybaseComputeRow) ? 
					"SybaseComputeRow": "SybaseReturnStatusRow",
					[[(NSObject *)value description] cString]);
			[resultsView sprintf:buffer];
			sprintf(buffer, "************************************\n");
			[resultsView sprintf:buffer];
			break;
		case SybaseReturnParameterRow:
			// Exercise for the reader: How to format this row 
			[resultsView sprintf:"SybaseReturnParameterRow: No data printed\n"];
			break;
		case SybaseRegularRow:
		default:
			break;
		}
	} 
	
		
	return self;
}

- clear:sender
{
    [resultsView clear:sender];
    [[queryView docView] selectText:self];
    return self;
}

- close:sender
{
    // close the adaptor channel and disconnect from the database  
	[channel closeChannel];
	// close the main window  
	[[NXApp mainWindow] performClose:sender];
	return self;
}
 


/*--------------------------------------------------------------------------
 *	Announcing Trace Info
 *------------------------------------------------------------------------*/
- (void) logString:(NSString *) aString
{
	[resultsView sprintf: "%s", [aString cString]];
}

- (void) announce:sender selector:(SEL)selector
{
	return [self announce:sender selector:selector with:nil];
}


- (void) announce:sender selector:(SEL)selector with:array
{
	NSMutableString	*string;
	
	string = [NSMutableString stringWithFormat:@"BEGIN> {Sender = %s%s",
		[[(NSObject *)[sender class] description] cString], "}" ];
	
			
	[string appendFormat:@" \n\t{Delegate Method = %s} ", sel_getName(selector)];

	if (array)
		{
		id object;
		id verboseString = [NSMutableString stringWithCapacity:0];
		id enumerator = [array objectEnumerator];
		
		while ((object = [enumerator nextObject]) != nil)
			[verboseString 
				appendFormat:@"\n\t{Argument = %s} \n\t{Argument Description = %s} ", 
				[[(NSObject *)[object class] description] cString], 
				[[(NSObject *) object description] cString]];

		[string appendString:verboseString];
		}

		
	[string appendFormat:@"\n"];
	[resultsView sprintf: "%s", [string cString]];
}


/* Window Delegate method -- constrain window resizing */

- windowWillResize:sender toSize:(NXSize *)frameSize
{
    if ( frameSize->height < 230 ) frameSize->height= 230;
    return self;
}


@end
